//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Packaging;
using System.Text;
using System.Xml;
using OpenLS.Core.Serialization;
using OpenLS.Drawing;
using OpenLS.Drawing.Serialization;
using OpenLS.Spreadsheet.Data;
using OpenLS.Spreadsheet.Pivot;
using OpenLS.Spreadsheet.Serialization;
using OpenLS.Vml.Serialization;

namespace OpenLS.Spreadsheet
{
    public partial class Workbook
    {
        internal IEnumerable<StandardConditionalFormattingRule> StandardRules
        {
            get
            {
                foreach (Sheet sheet in Sheets)
                {
                    var worksheet = sheet as Worksheet;
                    if (worksheet != null)
                        foreach (ConditionalFormatting f in worksheet.ConditionalFormatting)
                        {
                            foreach (ConditionalFormattingRule rule in f.Rules)
                            {
                                var sRule = rule as StandardConditionalFormattingRule;
                                if (sRule != null)
                                {
                                    yield return sRule;
                                }
                            }
                        }
                }
            }
        }

        #region IOfficeXmlSerializable Members

        void IOfficeXmlSerializable.ReadXml(ReadContext context)
        {
            var wr = new WorkbookResolver(this, false);
            context.AddData(wr);
            context.AddData(new Dictionary<Uri, object>());

            ReadConnections(context);

            foreach (PackageRelationship sharedStringR in context.Part.GetRelationshipsByType(sharedStringRelationship))
            {
                PackagePart sharedStringsPart =
                    context.Part.Package.GetPart(PackUriHelper.ResolvePartUri(context.Part.Uri, sharedStringR.TargetUri));
                SharedStrings = new SharedStrings();
                using (Stream stream = sharedStringsPart.GetStream())
                {
                    using (var xr = new XmlTextReader(stream))
                    {
                        var rc = new ReadContext(xr, sharedStringsPart, SpreadsheetConstants.SpreadsheetNamespaceUri);
                        AddToReadContext(rc);
                        SharedStrings = rc.ReadElement<SharedStrings>();
                    }
                }
            }
            foreach (
                PackageRelationship themeR in
                    context.Part.GetRelationshipsByType(DrawingConstants.ThemeRelationshipType))
            {
                PackagePart themePart =
                    context.Part.Package.GetPart(PackUriHelper.ResolvePartUri(context.Part.Uri, themeR.TargetUri));
                Theme = new Theme();
                using (Stream stream = themePart.GetStream())
                using (var xr = new XmlTextReader(stream))
                {
                    var rc = new ReadContext(xr, themePart, DrawingConstants.DrawingNamespaceUri);
                    rc.CopyDataFrom(context);
                    Theme = rc.ReadElement<Theme>();
                }
            }
            context.AddData(new DifferentialStyleRecordResolver(0));
            context.AddData(new StyleRecordManager());
            foreach (PackageRelationship styleSheetR in context.Part.GetRelationshipsByType(stylesRelationship))
            {
                using (
                    RelatedReadContext styleSheetReadContext = context.OpenRelatedContext(styleSheetR,
                                                                                          context.NamespaceUri))
                {
                    _styleSheet = new StyleSheet(this);
                    PackagePart styleSheetPart =
                        context.Part.Package.GetPart(PackUriHelper.ResolvePartUri(context.Part.Uri,
                                                                                  styleSheetR.TargetUri));
                    _styleSheet.ReadStyleSheet(styleSheetReadContext, styleSheetPart);
                }
            }
            using (ReadContext c = context.Read(xmlName))
            {
                Name = c.GetOptionalString("name");
                ReadContext rc = c; //\\ new ReadContext(reader, part);
                FileVersion = rc.ReadOptionalElement<FileVersion>("fileVersion");
                FileSharing = c.ReadOptionalElement<FileSharing>("fileSharing");
                Properties = c.ReadOptionalElement<WorkbookProperties>("workbookPr");

                var protection = c.ReadOptionalElement<WorkbookProtection>("workbookProtection");
                if (c.ReaderLocalName == "bookViews")
                {
                    using (ReadContext cc = c.Read("bookViews"))
                    {
                        while (cc.ReaderLocalName == "workbookView")
                        {
                            var vv = new WorkbookView(this);
                            vv.ReadXml(cc);
                            View = vv;
                        }
                    }
                }
                var manager = new InvalidSheetManager();
                context.AddData(manager);

                using (new WorkbookCalculationDisabler(this))
                {
                    using (new VmlReadContext(context))
                    {
                        ReadSheets(context);
                        if (!context.IsValid)
                            return;
                        if (Sheets.Count > 0)
                            View.ActiveSheet = Sheets[manager.GetValidIndex(View.ActiveTab)];
                    }
                }
                c.Eat("functionGroups");
                c.Eat("functionPrototypes");
                ReadExternalReferences(context);
                if (c.ReaderLocalName == "definedNames")
                {
                    using (ReadContext c2 = c.Read("definedNames"))
                    {
                        while (c2.ReaderLocalName == "definedName")
                        {
                            DefinedNames.Add(c2.ReadElement(() => new DefinedName(this)));
                        }
                    }
                }
                _calcPr = c.ReadOptionalElement<CalculationProperties>("calcPr");
                c.WarnIf("oleSize");

                _customWorkbookViews =
                    new List<CustomWorkbookView>(c.ReadElements<CustomWorkbookView>("customWorkbookViews",
                                                                                    "customWorkbookView"));

                //\\c.EatForNow("pivotCaches");
                object dummy = (c.ReadOptionalListElement<PivotCache>("pivotCaches", "pivotCache"));

                SmartTagProperties = c.ReadOptionalElement<SmartTagProperties>("smartTagPr");
                List<SmartTagType> tt = c.ReadOptionalListElement<SmartTagType>("smartTagTypes", "smartTagType");
                foreach (SmartTagType t in tt)
                    SmartTagTypes.Add(t);
                WebPublishing = c.ReadOptionalElement<WebPublishing>("webPublishing");
                FileRecoveryProperties.Clear();
                foreach (FileRecoveryProperties p in c.ReadOptionalWhile<FileRecoveryProperties>("fileRecoveryPr"))
                    FileRecoveryProperties.Add(p);
                WebPublishObjects.Clear();
                WebPublishObjects.AddRange(c.ReadElements<WebPublishObject>("webPublishObjects", "webPublishObject"));
                Extension = c.ReadOptionalElement<ExtensionElement>("extLst");
                c.WarnIf("fsb");
                CalculationMode m = CalculationProperties.CalculationMode;
                CalculationProperties.CalculationMode = CalculationMode.Automatic;
                context.GetData<WorkbookResolver>().ResolveAll();
                CalculationProperties.CalculationMode = m;
                Protection = protection;
            }
#if NET40
            ReadCustomXml(context);
#endif   
        }

        void IOfficeXmlSerializable.WriteXml(WriteContext context)
        {
            var pivotCacheManager = new PivotCacheManager();
            context.SetData(typeof (PivotCacheManager), pivotCacheManager);
            _sheetInfos = new List<SheetInfo>();
            context.SetData(typeof (Dictionary<object, Uri>), new Dictionary<object, Uri>());
            const string sharedStringsContentType =
                @"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml";
            const string stylesContentType = @"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml";
            context.SetData(typeof (WriteObjectTable<DifferentialStyleRecord>),
                            new WriteObjectTable<DifferentialStyleRecord>());
            SharedStrings = new SharedStrings();
            {
                var styleUri = new Uri("styles.xml", UriKind.Relative);
                /* PackageRelationship styleR = context.Part.CreateRelationship(styleUri, TargetMode.Internal, stylesRelationship);
                PackagePart styleP = context.Part.Package.CreatePart(PackUriHelper.ResolvePartUri(styleR.SourceUri, styleR.TargetUri), stylesContentType, CompressionOption.Maximum);
                using (Stream stream = styleP.GetStream())
                using (var w = new XmlTextWriter(stream, Encoding.UTF8))
                {
                    var wwc = new WriteContext(SpreadsheetConstants.Spreadsheet, w, styleP);
                    wwc.CopyDataFrom(context);
                    wwc.WriteElement(StyleSheet);
                }*/
                using (
                    RelatedWriteContext wwc = context.CreateRelatedContext(SpreadsheetConstants.Spreadsheet, styleUri,
                                                                           stylesRelationship, stylesContentType))
                {
                    wwc.CopyDataFrom(context);
                    wwc.WriteElement(_styleSheet);
                }
            }
            if (Theme != null)
            {
                var themeUri = new Uri("theme/theme1.xml", UriKind.Relative);
                PackageRelationship themeR = context.Part.CreateRelationship(themeUri, TargetMode.Internal,
                                                                             DrawingConstants.ThemeRelationshipType);
                PackagePart themeP =
                    context.Part.Package.CreatePart(PackUriHelper.ResolvePartUri(themeR.SourceUri, themeR.TargetUri),
                                                    DrawingConstants.ThemeContentType, CompressionOption.Maximum);
                using (Stream stream = themeP.GetStream())
                using (var w = new XmlTextWriter(stream, Encoding.UTF8))
                {
                    var wc = new WriteContext(DrawingConstants.DrawingNamespace, w, themeP);
                    wc.WriteElement(Theme);
                }
            }
            int sheetId = 1;
            context.SetData(typeof (TableCounter), new TableCounter());
            foreach (Sheet sheet in Sheets)
            {
                using (RelatedWriteContext wc = context.CreateRelatedContext(
                    null, SpreadsheetConstants.SpreadsheetNamespaceUri,
                    context.GetAvailableUri(sheet.UriPrefix + "{0}.xml"), sheet.RelationshipType, sheet.ContentType))
                {
                    wc.WriteElement(sheet);
                    var info = new SheetInfo {Name = sheet.Name};
                    info.CopyFrom(sheet);
                    info.SheetId = sheetId++;
                    info.RelationshipId = wc.Relationship.Id;
                    _sheetInfos.Add(info);
                }
            }
            using (WriteContext c = context.Write(xmlName))
            {
                c.DeclareNamespace("r", OfficeConstants.RelationshipNamespace.NamespaceUri);

                c.WriteOptionalElement(FileVersion);
                c.WriteOptionalElement(FileSharing);
                c.WriteOptionalElement(_workbookPr);
                c.WriteOptionalElement(Protection);
                if (View != null)
                {
                    using (WriteContext c2 = c.Write("bookViews"))
                    {
                        c2.WriteElement(View);
                    }
                }
                using (WriteContext sheetsW = c.Write("sheets"))
                {
                    foreach (SheetInfo sheetInfo in _sheetInfos)
                        sheetsW.WriteElement(sheetInfo);
                }
                WriteExternalReferences(c);
                c.WriteOptionalElementList(DefinedNames, "definedNames");
                c.WriteOptionalElement(_calcPr);

                c.WarnIf("oleSize");
                c.WriteOptionalElementList(CustomWorkbookViews, "customWorkbookViews");

                var pivotTables = new List<PivotTableDefinition>(
                    Enumerable.SelectMany(
                        Enumerable.OfType<Worksheet>(Sheets), sh => sh.PivotTables));
                if (pivotTables.Count > 0)
                {
                    var resolver = context.GetData<PivotCacheManager>();
                    using (WriteContext c2 = c.Write("pivotCaches"))
                    {
                        foreach (PivotTableDefinition definition in pivotTables)
                        {
                            var cache = new PivotCache();
                            cache.CacheId = definition.CacheId;
                            Uri uri = resolver[definition.PivotCacheDefinition];
                            PackageRelationship relationship =
                                context.Part.CreateRelationship(uri, TargetMode.Internal,
                                                                SpreadsheetConstants.PivotCacheDefinitionType);
                            cache.Id = relationship.Id;
                            c2.WriteElement(cache);
                        }
                    }
                }

                c.WriteOptionalElement(SmartTagProperties);
                if (SmartTagTypes.Count > 0)
                {
                    c.WriteOptionalElementList(SmartTagTypes, "smartTagTypes");
                }
                c.WriteOptionalElement(_webPublishing);
                foreach (FileRecoveryProperties p in FileRecoveryProperties)
                    c.WriteElement(p);
                if (WebPublishObjects.Count > 0)
                {
                    using (WriteContext c2 = c.Write("webPublishObjects"))
                    {
                        foreach (WebPublishObject o in WebPublishObjects)
                        {
                            c2.WriteElement(o);
                        }
                    }
                }
                c.WriteOptionalElement(Extension);
            }
            if (SharedStrings.Count > 0)
            {
                var sharedStrings = new Uri("sharedStrings.xml", UriKind.Relative);
                PackageRelationship sharedStringsR = context.Part.CreateRelationship(sharedStrings, TargetMode.Internal,
                                                                                     sharedStringRelationship);
                PackagePart sharedStringP =
                    context.Part.Package.CreatePart(
                        PackUriHelper.ResolvePartUri(sharedStringsR.SourceUri, sharedStringsR.TargetUri),
                        sharedStringsContentType, CompressionOption.Maximum);
                using (Stream stream = sharedStringP.GetStream())
                using (XmlWriter w = new XmlTextWriter(stream, Encoding.UTF8))
                {
                    var wcc = new WriteContext(SpreadsheetConstants.Spreadsheet, w, sharedStringP);
                    wcc.WriteElement(SharedStrings);
                }
            }
            WriteConnections(context);
#if NET40
            WriteCustomXml(context);
#endif
        }

        #endregion

        private void ReadSheets(ReadContext context)
        {
            var manager = context.GetData<InvalidSheetManager>();
            _sheetInfos = new List<SheetInfo>();
            if (context.ReaderLocalName == "sheets")
            {
                using (ReadContext c = context.Read("sheets"))
                {
                    while (c.ReaderLocalName == "sheet")
                    {
                        try
                        {
                            _sheetInfos.Add(c.ReadElement<SheetInfo>());
                            manager.Add(true);
                        }
                        catch (IgnoreElementException)
                        {
                            manager.Add(false);
                        }
                    }
                }
            }

            foreach (SheetInfo sheetInfo in _sheetInfos)
            {
                Contract.Assert(sheetInfo != null);
                Contract.Assert(context != null);
                Contract.Assert(sheetInfo.RelationshipId != null);
                using (
                    ReadContext rc = context.OpenRelatedContext(context.Part.GetRelationship(sheetInfo.RelationshipId),
                                                                SpreadsheetConstants.SpreadsheetNamespaceUri))
                {
                    //\\                    rc.CopyDataFrom(context);
                    switch (rc.ReaderLocalName)
                    {
                        case "worksheet":
                            Sheets.Add(rc.ReadElement(() => new Worksheet(this, sheetInfo)));
                            break;
                        case "chartsheet":
                            Sheets.Add(rc.ReadElement(() => new Chartsheet(this, sheetInfo)));
                            break;
                        case "dialogsheet":
                            Sheets.Add(rc.ReadElement(() => new Dialogsheet(this, sheetInfo)));
                            break;
                        default:
                            throw new NotImplementedException();
                    }
                    if (!context.IsValid)
                        return;
                }
            }
        }


        private void ReadExternalReferences(ReadContext context)
        {
            List<ExternalReference> refs = context.ReadOptionalListElement<ExternalReference>("externalReferences",
                                                                                              "externalReference");
            if (refs != null)
            {
                foreach (ExternalReference externalReference in refs)
                {
                    using (
                        ReadContext c = context.OpenRelatedContext(externalReference.Id,
                                                                   SpreadsheetConstants.SpreadsheetNamespaceUri))
                    {
                        ExternalLinks.Add(c.ReadElement(() => new ExternalLink(ExternalLinks.Count)));
                    }
                }
            }
        }

        private void ReadConnections(ReadContext context)
        {
            Connections.Clear();
            foreach (
                PackageRelationship relationship in
                    context.Part.GetRelationshipsByType(SpreadsheetConstants.ConnectionRelationshipType))
            {
                using (
                    ReadContext ctx = context.OpenRelatedContext(relationship,
                                                                 SpreadsheetConstants.SpreadsheetNamespaceUri))
                {
                    using (ReadContext ctx2 = ctx.Read("connections"))
                    {
                        while (ctx2.ReaderLocalName == "connection")
                            Connections.Add(DataConnection.ReadConnection(ctx2));
                    }
                }
            }
        }


        private void WriteExternalReferences(WriteContext context)
        {
            if (ExternalLinks.Count == 0)
                return;
            using (WriteContext c = context.Write("externalReferences"))
            {
                foreach (ExternalLink link in ExternalLinks)
                {
                    using (
                        RelatedWriteContext c2 = c.CreateRelatedContext(null,
                                                                        SpreadsheetConstants.SpreadsheetNamespaceUri,
                                                                        c.GetAvailableUri(
                                                                            "externalLinks/externalLink{0}.xml"),
                                                                        SpreadsheetConstants.
                                                                            ExternalLinkRelationshipType,
                                                                        SpreadsheetConstants.ExternalLinkContentType))
                    {
                        c2.WriteElement(link);
                        var r = new ExternalReference {Id = c2.Relationship.Id};
                        c.WriteElement(r);
                    }
                }
            }
        }


        private void WriteConnections(WriteContext context)
        {
            if (Connections.Count == 0)
                return;

            using (
                RelatedWriteContext wc = context.CreateRelatedContext(null,
                                                                      SpreadsheetConstants.SpreadsheetNamespaceUri,
                                                                      new Uri("./connections.xml", UriKind.Relative),
                                                                      SpreadsheetConstants.ConnectionRelationshipType,
                                                                      SpreadsheetConstants.ConnectionContentType))
            {
                using (WriteContext wcconn = wc.Write("connections"))
                {
                    foreach (DataConnection connection in Connections)
                    {
                        wcconn.WriteElement(connection);
                    }
                }
            }
        }
    }
}